﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

namespace ALDIOrganizer
{
    public partial class Shift
    {

        public static string ToString(ShiftTandem shifts, bool absolute)
        {
            if ((shifts == null) || (shifts.Count == 0)) return String.Empty;

            var convertedToString = new string[shifts.Count];

            for (var i = 0; i < shifts.Count; i++)
            {
                convertedToString[i] = absolute ?
                    shifts[i + 1].ToStringAbsolute() :
                    shifts[i + 1].ToString();
            }

            return String.Join(", " + Environment.NewLine, convertedToString);
        }

        

        private static Mismatch MismatchSingle(Shift s, WorkingHoursSpan hrs)
        {
            var start = s.Start.Time;
            var end = s.Finish.Time;

            if (s.opening)
            {
                start = hrs.Open.Time;
            }
            if (s.closing)
            {
                end = hrs.Close.Time;
            }
            if (start >= end)
            {
                return Mismatch.OutCompletely;
            }
            if (start < hrs.Open.Time)
            {
                return Mismatch.SticksOut;
            }
            if (end > hrs.Close.Time)
            {
                return Mismatch.SticksOut;
            }

            return Mismatch.None;
        }

        public static Dictionary<Shift, Mismatch> GetMismatchedShifts(Shift[] shifts, WorkingHoursSpan hours)
        {
            var result = new Dictionary<Shift, Mismatch>();
            
            foreach (Shift s in shifts)
            {
                var status = MismatchSingle(s, hours);
                if (status != Mismatch.None)                                    
                    result.Add(s, status);                                
            }

            return result;
        }

        public static Mismatch WhatMismatch(Shift[] ls, WorkingHoursSpan hrs)
        {
            if (ls == null ||
                ls.Length == 0)
            {
                return Mismatch.None;
            }
            bool anyBeyond = false;
            foreach (var s in ls)
                switch (MismatchSingle(s, hrs))
                {
                    case Mismatch.None:
                        {
                            break;
                        }
                    case Mismatch.SticksOut:
                        {
                            anyBeyond = true;
                            break;
                        }
                    case Mismatch.OutCompletely:
                        {
                            return Mismatch.OutCompletely;
                        }
                    default:
                        {
                            break;
                        }
                }

            return anyBeyond ? Mismatch.SticksOut : Mismatch.None;
        }

        private static bool DoOverlap(Shift s1, Shift s2)
        {
            // if s1 starts DURING s2...
            if ((s1.Start.CompareTo(s2.Start) >= 0) &&
                (s1.Start.CompareTo(s2.Finish) < 0))
            {
                return true;
            }
            // or vice versa, as the case might be
            if ((s2.Start.CompareTo(s1.Start) >= 0) &&
                (s2.Start.CompareTo(s1.Finish) < 0))
            {
                return true;
            }
            return false;
        }

        // for more than two Shifts. 
        public static bool DoOverlap(Shift[] allShifts)
        {
            // if there'pShift just 1 or... 0, we've got nothing to look for really
            if (allShifts.Length < 2) return false;

            // all vs all
            return (from s1 in allShifts
                    from s2 in allShifts
                    where s1 != s2
                    where DoOverlap(s1, s2)
                    select s1).Any();
        }

        /* Basing On a list of Shifts (passed in the parameter),
         * it returns the SMALLEST number of emps_from_database present
         * at the store during that Day
         * (if it'pShift 0, it means that at some point the store would be Empty */
        public static KeyValuePair<sbyte, sbyte> min_maxEmployees(List<Shift> allShifts, WorkingHoursSpan hrs)
        {

            if ((allShifts == null) || (allShifts.Count == 0)) return new KeyValuePair<sbyte, sbyte>(0, 0);

            var sd = new SortedDictionary<byte, sbyte>();

            foreach (var s in allShifts)
            {
                var start = s.Start.Time;
                var end = s.Finish.Time;

                // row comes...
                if (sd.ContainsKey(start)) // if some row comes at the same Time...
                    sd[start] += 1;       // AddAnotherRowString another
                else
                    sd.Add(start, 1);   // if that'pShift the id1 one - create a new pEntry

                // row goes...
                if (sd.ContainsKey(end)) // if some row comes at the same Time...
                    sd[end] -= 1;       // AddAnotherRowString another
                else
                    sd.Add(end, -1);   // if that'pShift the id1 one - create a new pEntry                
            }

            // Since it'pShift a sorted dictionary, it automatically gets sorted in chronological order


            sbyte minimum = 0;
            sbyte maximum = 0;
            sbyte current = 0;

            bool first_occurence = true;
            byte last_occurence = 0;

            foreach (KeyValuePair<byte, sbyte> kv in sd)
            {
                current += kv.Value; // 2 people came at 8:00? one person goes at 12:00? it'pShift reflected in the Value of current

                if (first_occurence)
                    if (kv.Key > hrs.Open.Time) // and it'pShift after the store was Open, it means it had been Empty for a while
                        minimum = 0;           // so the minimum number of people is 0. in other words, at some Time of the Day, there was noone in the store.
                    else
                        minimum = kv.Value;

                /* what if somebody comes to the store earlier - candidate.g. 7am, When the working hours are 8:00-20:30
                 * - in the way of pException...
                 * and the id2 person starts as normal, at 8:00
                 * 
                 * is the minimum 1 person? not really. we only count the minimum number of people
                 * WITHIN THE WORKING HOURS.
                 * If Tom comes earlier, that'pShift his business. We Start counting people at 8'o'clock. */
                if ((!first_occurence) && (kv.Key == hrs.Open.Time))
                    minimum = current;
                first_occurence = false;

                last_occurence = kv.Key; // we need to preserve this Value, or else we'll forget...

                if (current > maximum) maximum = current;
                if ((current < minimum) && (last_occurence < hrs.Close.Time)) minimum = current;
            }

            // now everybody left, and we need to look at the watch: does the store Close?
            // or have they gone home to early.
            if (last_occurence < hrs.Close.Time) minimum = 0;

            return new KeyValuePair<sbyte, sbyte>(minimum, maximum);
        }

        public static Shift ToShift(string input, Sheet pRefill, bool tryAmPm)
        {
            /* If conversion is not possible, it returns the nullable with no Value.
             * The caller will find out about the MyCellType WhatMismatch
             * by examining the HasValue field. */
            if (!Shift.IsShift(input)) return null;

            Shift _result = new Shift();
            _result.Parent = pRefill;

            /* From now On we're playing safe, because we know
             * that input matches the pFormat */

            input = input.Replace(" ", String.Empty);

            var _fromTo = input.Split(new char[] { '-' });
            string from = _fromTo[0];
            string to = _fromTo[1];

            if (from == "op")
            {
                _result.opening = true;
            }
            else
            {
                /* we don't worry whether parsing was successful -
                 * we know it must have been, since the input for the shift as such
                 * (eg. "8:00 - 14" was already verified with regular expression
                 * beforehand) */
                _result.Start = TimeByQuarters.Parse(from).Value;
            }
            if (to == "cl")
            {
                _result.closing = true;
            }
            else
            {
                _result.Finish = TimeByQuarters.Parse(to).Value;
            }

            // If the Shift seems to Finish before it starts (candidate.g. 8 - 3)...
            if (_result.Finish.Time <= _result.Start.Time)
            {
                if (tryAmPm)
                {
                    // First check, if it'pShift not am-pm. If the hour is earlier than 12:00, it might be...
                    if ((_result.Finish.Time < (12 * 4)))
                    {
                        /* If it STILL doesn't work...
                         * for example, the input was "14 - 1".
                         * We can optimistically assume that When our user wrote "1", he really meant "13"...
                         * But 14 - 13 still makes no sense, the row can't be moving backward in Time. */
                        if (_result.Finish.Time + (12 * 4) <=
                            _result.Start.Time)
                        {
                            throw new ArgumentException();
                        }
                        var hr = default(TimeByQuarters);
                        hr.Time = (byte)(_result.Finish.Time + (12 * 4));
                        _result.Finish = hr;
                    }
                }
                else
                {
                    throw new ArgumentException();
                }
            }

            return _result;
        }

        public static Shift ToShift(string input)
        {
            return ToShift(input, null);
        }

        public static Shift ToShift(string input, Sheet pRefill)
        {
            return ToShift(input, pRefill, true);
        }

        public static ShiftTandem ToShifts(string input, Sheet pRefill)
        {
            if (!AreShifts(input))
            {
                return null;
            }
            var _shifts = new ShiftTandem();

            input = input.Replace(" ", String.Empty);
            var _inputs = input.Split(new[] { ',' });

            foreach (string bit in _inputs)
            {
                var s = ToShift(bit, pRefill);
                if (s == null)
                {
                    return null;
                }
                _shifts.Add(s);
            }
            return _shifts;
        }

        /* For manually inputting a Shift.
         * 
         * Uses 24h Time.
         * 
         * Allows for any number of Blank spaces anywhere.
         * 
         * Example matches:
         * 
         * 08:00-16
         * 19:15-23
         *    9 - 17 : 45
         *    
         * It also allows using "cl" acronyme (which means "until Close").
         * E.g.
         * 14:30 - cl
         * 
         * It will NOT match:
         * 9:20 -  15
         * (only :00, :15, :30, :45)         */
        // public const string _shiftReg = @"^[\pShift]*(([1-9]|[1]\d|[0]\d|2[0-3])|result)[\pShift]*(:[\pShift]*(00|15|30|45)[\pShift]*)?-[\pShift]*(([1-9]|[1]\d|[0]\d|2[0-3])[\pShift]*(:[\pShift]*(00|15|30|45)[\pShift]*)?|cl)[\pShift]*$";




        public static bool AreShifts(string input)
        {
            if (!(IsShift(input) || input.Contains(','))) return false;

            input = input.Replace(" ", String.Empty);
            var bits = input.Split(new[] { ',' });

            return bits.All(bit => IsShift(bit));
        }        


    }
}
    


        
    
    

